use core::{slice, str};

use alloc::{string::String, vec::Vec};
use syscall::scheme::{FileInfo, SchemeTy};
use x86_64::{
    structures::paging::{Mapper, Page, PageTableFlags, PhysFrame, Size4KiB},
    PhysAddr, VirtAddr,
};

use crate::{
    context::{
        get_current_process_id,
        process::{is_process_exited, Process},
        scheduler::SCHEDULER,
    },
    memory::{
        addr_to_mut_ref, ref_current_page_table, write_for_syscall, DmaManager, FRAME_ALLOCATOR,
    },
    scheme::{
        op::{get_scheme_by_fd, OpenMode},
        user::INIT_PROC_PID,
    },
};

pub fn print(buffer: *const u8, length: usize) -> usize {
    if length == 0 {
        return 0;
    }

    if let Ok(string) = unsafe {
        let slice = slice::from_raw_parts(buffer, length);
        str::from_utf8(slice)
    } {
        crate::serial_print!("{}", string);
        crate::print!("{}", string);
    };

    0
}

pub fn exit() -> usize {
    {
        let current_thread = {
            let mut scheduler = SCHEDULER.lock();
            let thread = scheduler.current_thread();
            scheduler.remove(thread.clone());
            thread
        };

        if let Some(current_thread) = current_thread.upgrade() {
            let current_thread = current_thread.read();
            if let Some(process) = current_thread.process.upgrade() {
                process.read().exit_process();
            }
        }
    }

    0
}

pub fn open(path: usize, path_len: usize, mode: usize) -> usize {
    let slice = unsafe { core::slice::from_raw_parts(path as _, path_len) };
    let path = String::from(str::from_utf8(slice).expect("Cannot from utf8"));

    if let Some(ret) = crate::scheme::op::open(path.clone(), OpenMode::from(mode)) {
        return ret;
    }
    usize::MAX
}

pub fn close(fd: usize) -> usize {
    crate::scheme::op::close(fd);
    0
}

pub fn read(fd: usize, buf: usize, buf_size: usize) -> usize {
    let buffer = unsafe { slice::from_raw_parts_mut(buf as _, buf_size) };
    crate::scheme::op::read(fd, buffer)
}

pub fn write(fd: usize, buf: usize, buf_size: usize) -> usize {
    let buffer = unsafe { slice::from_raw_parts(buf as _, buf_size) };
    crate::scheme::op::write(fd, buffer)
}

pub fn fsize(fd: usize) -> usize {
    crate::scheme::op::fsize(fd).unwrap()
}

pub fn is_exited(pid: usize) -> usize {
    is_process_exited(pid) as usize
}

pub fn ftype(fd: usize) -> usize {
    let scheme = get_scheme_by_fd(fd);

    if let Some(scheme) = scheme {
        return scheme.read().scheme_type() as usize;
    }

    usize::MAX
}

pub fn list_dir(path_addr: usize, path_len: usize, buf_addr: usize) -> usize {
    let buf = unsafe { slice::from_raw_parts(path_addr as _, path_len) };
    let path = String::from(core::str::from_utf8(buf).unwrap());

    #[derive(Clone)]
    #[allow(dead_code)]
    struct TemporyInfo {
        name: &'static [u8],
        ty: SchemeTy,
    }

    let file_infos: Vec<TemporyInfo> = {
        let infos = crate::scheme::op::list_dir(path);
        let mut new_infos = Vec::new();
        for info in infos.iter() {
            let FileInfo { name, ty } = info;
            let new_name = alloc::vec![0u8; name.len()].leak();
            new_name[..name.len()].copy_from_slice(name.as_bytes());
            new_infos.push(TemporyInfo {
                name: new_name,
                ty: *ty,
            });
        }
        new_infos
    };

    write_for_syscall(VirtAddr::new(buf_addr as u64), file_infos.as_slice());

    0
}

pub fn dir_item_num(path_addr: usize, path_len: usize) -> usize {
    let buf = unsafe { slice::from_raw_parts(path_addr as _, path_len) };
    let path = String::from(core::str::from_utf8(buf).unwrap());

    crate::scheme::op::list_dir(path).len()
}

pub fn ioctl(fd: usize, cmd: usize, arg: usize) -> usize {
    crate::scheme::op::ioctl(fd, cmd, arg)
}

pub fn lseek(fd: usize, offset: usize) -> usize {
    let res = crate::scheme::op::lseek(fd, offset);
    if res.is_some() {
        return 0;
    }
    return usize::MAX;
}

pub fn mmap(vaddr: usize, paddr: usize, len: usize, flags: usize) -> usize {
    let mut page_table = unsafe { ref_current_page_table() };
    for i in 0..((len + 4095) / 4096) {
        let result = unsafe {
            page_table.map_to(
                Page::<Size4KiB>::containing_address(VirtAddr::new((vaddr + i * 4096) as u64)),
                PhysFrame::containing_address(PhysAddr::new(paddr as u64)),
                PageTableFlags::from_bits_retain(flags as u64),
                &mut *FRAME_ALLOCATOR.lock(),
            )
        };

        match result {
            Ok(f) => f.flush(),
            Err(e) => match e {
                x86_64::structures::paging::mapper::MapToError::PageAlreadyMapped(_) => {
                    page_table
                        .unmap(Page::<Size4KiB>::containing_address(VirtAddr::new(
                            (vaddr + i * 4096) as u64,
                        )))
                        .unwrap()
                        .1
                        .flush();
                    unsafe {
                        page_table
                            .map_to(
                                Page::<Size4KiB>::containing_address(VirtAddr::new(
                                    (vaddr + i * 4096) as u64,
                                )),
                                PhysFrame::containing_address(PhysAddr::new(paddr as u64)),
                                PageTableFlags::from_bits(flags as u64).unwrap(),
                                &mut *FRAME_ALLOCATOR.lock(),
                            )
                            .unwrap()
                            .flush();
                    }
                }
                _ => panic!("Cannot do mmap"),
            },
        }
    }

    vaddr
}

pub fn alloc_for_dma(size: usize) -> usize {
    let addr = DmaManager::allocate(size).0.as_u64() as usize;
    mmap(addr, addr, size, 0x7);
    addr
}

pub fn dealloc_for_dma(addr: usize, _size: usize) -> usize {
    DmaManager::deallocate(VirtAddr::new(addr as u64));
    return 0;
}

pub fn getpid() -> usize {
    get_current_process_id().0 as usize
}

pub fn set_init_pid(pid: usize) -> usize {
    unsafe { INIT_PROC_PID = pid };
    pid
}

pub fn execve(buf_addr: usize, buf_size: usize, argc: usize, argv: usize) -> usize {
    let buf = unsafe { core::slice::from_raw_parts(buf_addr as *const u8, buf_size) };
    return Process::new_user_process("process", buf, argc, argv);
}

pub fn change_cwd(path_addr: usize, path_len: usize) -> usize {
    let slice = unsafe { core::slice::from_raw_parts(path_addr as _, path_len) };
    let path = String::from(str::from_utf8(slice).expect("Cannot from utf8"));
    crate::scheme::op::change_cwd(path);
    0
}

pub fn get_cwd() -> usize {
    let path = crate::scheme::op::get_cwd();
    let new_path = alloc::vec![0u8; path.len()].leak();
    new_path[..path.len()].copy_from_slice(path.as_bytes());
    let ret_struct_ptr = alloc::vec![0u8; 16].leak().as_ptr() as u64;
    let path_ptr = addr_to_mut_ref(VirtAddr::new(ret_struct_ptr));
    *path_ptr = new_path;
    let len_ptr = addr_to_mut_ref(VirtAddr::new(ret_struct_ptr + 8));
    *len_ptr = path.len();
    ret_struct_ptr as usize
}
